<?php if(!defined('BASEPATH')) exit('No direct access to this file allowed');

/******************************************************************************\
 *  ComponentFactory
 *
 *  Manufactures core system components for consumption. This factory reads
 *  from a system configuration file to determine which class to manufacture.
 *
 *  Change Log
 *  0.1     -   Initial version of ComponentFactory
 *
 *  @package    core
 *  @author     Ruben Tan
 *  @copyright  Copyright (c) 2008 Rendervault
\******************************************************************************/
class ComponentFactory
{
    private static $_instance = null;
    private $_components = array();
    private $_extensions = array();
    private $_lookup = null;
    
    /******************************************************************************\
     *  ComponentFactory constructor
     *  @access     private
    \******************************************************************************/
    private function __construct( ) 
    { 
        // initialize components
        $this->_lookup = new ComponentLookup();
        $this->_components = $this->_lookup->get_core_components();
    }
    
    /******************************************************************************\
     *  ComponentFactory constructor
     *  @access     private
    \******************************************************************************/
    private function __clone( ) 
    { }
    
    /******************************************************************************\
     *  Returns a singleton instance
     *
     *  @return     instance    A reference to the static ComponentFactory instance
    \******************************************************************************/
    public static function &get_instance( ) 
    {
        if(!isset(ComponentFactory::$_instance))
        {
            ComponentFactory::$_instance = new ComponentFactory();
        }
        
        return ComponentFactory::$_instance;
    }
    
    /******************************************************************************\
     *  Loads extension components
     *
     *  @param      array       An associative array telling IgnitionBase which
     *                          extension to load to replace the existing core
     *                          component 
     *                          (e.g. array('Config' => 'system.ext.NewConfig'))
    \******************************************************************************/
    public function load_extensions( Array $extensions )
    {
        if(!isset($extensions) || !is_array($extensions) || count($extensions) == 0) return;
        
        $this->_extensions = $extensions;
    }
    
    /******************************************************************************\
     *  Loads a component. First it will check if the component specified is a core
     *  component. If so, it will load the core component's interface too. If not,
     *  it will load 
     *
     *  @param      string      The component name to load
     *  @return     Component   An instance of the component or NULL if none
    \******************************************************************************/
    public function load_component( $component, $has_interface = FALSE )
    {        
        $is_core = FALSE;
        $class_path = '';
        
        // check if it is in the extensions array first
        if(isset($this->_extensions[$component]))
        {
            $is_core = FALSE;
            $class_path = $this->_extensions[$component];
        }
        elseif(isset($this->_components[$component]))
        {
            $is_core = TRUE;
            $class_path = $this->_components[$component];
        }
        else
        {
            $is_core = FALSE;
            $class_path = $component;
        }
        
        // check if it exists
        $class = explode(NS, $class_path);
        $class_name = $class[count($class) - 1];
        $interface_name = $class_name . 'Interface';
        
        $real_path = BASEPATH . implode(SP, $class);
        
        //ob_start();
        // Loads the interface as well by adding a 'Interface' suffix to the class name
        if($has_interface || $is_core)
        {
            if(file_exists($real_path . 'Interface' . EXT) === FALSE) 
            {
                //@ob_end_clean();
                throw new Exception('Unable to locate ' . $real_path . 'Interface' . EXT);
            }
            
            include_once $real_path . 'Interface' . EXT;
            
            if(!interface_exists($interface_name)) 
            {
                //@ob_end_clean();
                throw new Exception($interface_name . ' does not exist in ' . $real_path . 'Interface' . EXT);
            }
        }
        
        if(file_exists($real_path . EXT) === FALSE) 
        {
            //@ob_end_clean();
            throw new Exception('Unable to locate ' . $real_path . EXT);
        }
        
        include_once $real_path . EXT;
        
        if(!class_exists($class_name)) 
        {
            //@ob_end_clean();
            throw new Exception($class_name . ' does not exist in ' . $real_path . EXT);
        }
        
        // instantiate it and check if it implements the Component properly
        $obj = new $class_name;
        if(! $obj instanceof Component) 
        {
            //@ob_end_clean();
            throw new Exception($component . ' must be sub-classed from Component');
        }
        
        if($has_interface || $is_core)
        {
            if(! $obj instanceof $interface_name) 
            {
                //@ob_end_clean();
                throw new Exception($component . ' must implement ' . $component. 'Interface');
            }
        }
        
        //@ob_end_clean();
        return $obj;
    }
}